package com.self.smallma.myblogweb.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.self.smallma.myblogweb.common.utils.PageUtils;
import com.self.smallma.myblogweb.common.utils.Query;
import com.self.smallma.myblogweb.dao.CommentDao;
import com.self.smallma.myblogweb.dao.MessageDao;
import com.self.smallma.myblogweb.service.*;
import com.self.smallma.myblogweb.util.MarkdownUtils;
import com.self.smallma.myblogweb.vo.BlogAndUserVo;
import com.self.smallma.myblogweb.vo.DetailedBlogVo;
import com.self.smallma.myblogweb.vo.FirstPageBlogVo;
import com.self.smallma.myblogweb.vo.RecommendBlogVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;


import com.self.smallma.myblogweb.dao.BlogDao;
import com.self.smallma.myblogweb.entity.BlogEntity;
import org.springframework.util.StringUtils;


@Service("blogService")
public class BlogServiceImpl extends ServiceImpl<BlogDao, BlogEntity> implements BlogService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    BlogService blogService;
    @Autowired
    BlogTypeRelationService blogTypeRelationService;
    @Autowired
    TypeService typeService;
    @Autowired
    UserService userService;
    @Autowired
    MessageService messageService;
    @Autowired
    MessageDao messageDao;
    @Autowired
    CommentDao commentDao;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<BlogEntity> page = this.page(
                new Query<BlogEntity>().getPage(params),
                new QueryWrapper<BlogEntity>()
        );

        return new PageUtils(page);
    }


    /**
     *  //加入缓存，给缓存中放入json字符串，拿出的json字符串还能逆转为能用的对象类型  【序列化与反序列化】
     *  产生堆外内存溢出：
     *  springboot2以后默认使用lettuce作为redis的客户端，他使用netty进行网络通信
     *  lettuce的bug导致
     *  解决方案：  （不能只调大堆外内存）
     *      1.升级lettuce客户端
     *      2.切换使用jedis客户端
     *   lettuce、jedis操作redis的底层客户端 spring再次封装
     * @param map
     * @param id
     * @return
     */
    /**
     * 高并发下缓存失效的问题：缓存击穿、缓存穿透、缓存雪崩
     * 解决方案：
     *      1.添加空结果：解决缓存击穿
     *      2.设置过期时间（加随机值）：解决缓存雪崩
     *      3.加锁：解决缓存击穿问题
     *  加锁：
     *   只要是同一把锁，就能锁住需要这个锁的所有线程、
     *     1.  synchronized (this) ；springboot的所有组件在容器中是单例的 【这个方法对单体应用可以，单体应用就是项目部署在一台服务器上，不进行分布式】
     * @param map
     * @param id
     * @return
     */
    @Override
    public PageUtils listByShow(Map<String, Object> map, Long id) {

        String blogsJson = stringRedisTemplate.opsForValue().get("blogsJson"+id.toString());
        if (StringUtils.isEmpty(blogsJson)){
            //如果缓存中没有，进入数据库查询
            PageUtils pageUtils = listByShowFromDb(map, id);
            //查到的数据再放入到缓存中，将对象转为json放入
            String s = JSON.toJSONString(pageUtils);
            stringRedisTemplate.opsForValue().set("blogsJson"+id.toString(),s,3, TimeUnit.HOURS);
            return pageUtils;
        }
        //转为指定的对象返回
        //new TypeReference<PageUtils>(){}  采用匿名内部类
        PageUtils result =  JSON.parseObject(blogsJson,new TypeReference<PageUtils>(){});
//        result.getList().forEach(System.out::println);
        return result;
    }


    public  PageUtils listByShowFromDb(Map<String, Object> map, Long id) {

        synchronized (this){
            //得到锁之后，我们应该再去缓存中确定一次，如果没有才继续
            String blogsJson = stringRedisTemplate.opsForValue().get("blogsJson"+id.toString());
            if (!StringUtils.isEmpty(blogsJson)){
                //缓存不为空，直接返回
                PageUtils result =  JSON.parseObject(blogsJson,new TypeReference<PageUtils>(){});
                return result;
            }
            QueryWrapper<BlogEntity> wrapper = new QueryWrapper<BlogEntity>().eq("type_id", id);
            wrapper.and(w->{
                w.eq("published",1);
            });
            IPage<BlogEntity> page = this.page(
                    new Query<BlogEntity>().getPage(map),
                    wrapper
            );
            PageUtils pageUtils = new PageUtils(page);
            List<BlogEntity> records = page.getRecords();
            List<FirstPageBlogVo> collect = records.stream().map(item -> {
                FirstPageBlogVo firstPageBlogVo = new FirstPageBlogVo();
                BeanUtils.copyProperties(item, firstPageBlogVo);
                firstPageBlogVo.setTypeName(typeService.getById(id).getName());
                firstPageBlogVo.setAvatar(userService.getById(item.getUserId()).getAvatar());
                firstPageBlogVo.setNickname(userService.getById(item.getUserId()).getNickname());
                return firstPageBlogVo;
            }).collect(Collectors.toList());

            pageUtils.setList(collect);
            return pageUtils;
        }


    }



    @Override
    public PageUtils getAllFirstPageBlog(Map<String, Object> map) {

//        return this.baseMapper.getFirstPageBlog();
        QueryWrapper<BlogEntity> wrapper = new QueryWrapper<BlogEntity>().orderByAsc("update_time");
        wrapper.and(w->{
            w.eq("published",1);
        });

        IPage<BlogEntity> page = this.page(
                new Query<BlogEntity>().getPage(map),
                wrapper
                );
        PageUtils pageUtils = new PageUtils(page);
        List<BlogEntity> list = page.getRecords();

        List<FirstPageBlogVo> collect = list.stream().map(item -> {
            FirstPageBlogVo firstPageBlogVo = new FirstPageBlogVo();
            BeanUtils.copyProperties(item, firstPageBlogVo);
            firstPageBlogVo.setTypeName(typeService.getById(item.getTypeId()).getName());
            firstPageBlogVo.setNickname(userService.getById(item.getUserId()).getNickname());
            firstPageBlogVo.setAvatar(userService.getById(item.getUserId()).getAvatar());
            return firstPageBlogVo;
        }).collect(Collectors.toList());
        pageUtils.setList(collect);
        return pageUtils;

    }

    @Override
    public BlogEntity saveWithCustom(FirstPageBlogVo blog) {

        BlogEntity blogEntity = new BlogEntity();
        BeanUtils.copyProperties(blog,blogEntity);
        blogEntity.setCreateTime(new Date());
        blogEntity.setUpdateTime(new Date());
        blogEntity.setShareStatement(0);
        blogEntity.setAppreciation(0);
        blogEntity.setCommentabled(0);
        blogEntity.setPublished(0);
        blogEntity.setRecommend(0);
        blogEntity.setViews(0);
        blogEntity.setUserId(1L);
        //TODO 添加评论次数
        blogEntity.setCommentCount(0);
        //没必要写，懒得改了
        blogEntity.setTypeName(typeService.getById(blog.getTypeId()).getName());
        this.save(blogEntity);
        return blogEntity;
    }

    @Override
    public BlogEntity updateBatchByIdWithUpdateTime(FirstPageBlogVo blog) {
        BlogEntity blogEntity = new BlogEntity();
        BeanUtils.copyProperties(blog,blogEntity);
        blogEntity.setUpdateTime(new Date());
        this.updateById(blogEntity);
        return blogEntity;
    }

    @Override
    public List<RecommendBlogVo> getRecommendedBlog() {
        QueryWrapper<BlogEntity> wrapper = new QueryWrapper<BlogEntity>().eq("recommend", 1);
        wrapper.and(w->{
            w.eq("published",1);
        });
        List<BlogEntity> recommend = this.list(wrapper);
        List<RecommendBlogVo> collect = recommend.stream().map(item -> {
            RecommendBlogVo recommendBlogVo = new RecommendBlogVo();
            BeanUtils.copyProperties(item, recommendBlogVo);
            return recommendBlogVo;
        }).collect(Collectors.toList());
        return collect;
    }
    @Override
    public PageUtils getSearchBlog(Map<String, Object> params,String key){

        String searchBlogsJson = stringRedisTemplate.opsForValue().get("searchBlogsJson"+key);
        if (StringUtils.isEmpty(searchBlogsJson)){
            //如果缓存中没有
            PageUtils searchBlogFromDb = getSearchBlogFromDb(params, key);
            String s = JSON.toJSONString(searchBlogFromDb);
            stringRedisTemplate.opsForValue().set("searchBlogsJson"+key,s,3,TimeUnit.HOURS);
            System.out.println("------返回数据库中的数据----------");
            return searchBlogFromDb;
        }
        //如果缓存中有，返回json字符串
        PageUtils result =  JSON.parseObject(searchBlogsJson,new TypeReference<PageUtils>(){});
        System.out.println("------返回Redis中的数据----------");
        return result;

    }


    public PageUtils getSearchBlogFromDb(Map<String, Object> params,String key) {

       synchronized (this){
           String searchBlogsJson = stringRedisTemplate.opsForValue().get("searchBlogsJson"+key);
           if (!StringUtils.isEmpty(searchBlogsJson)){
               System.out.println("------返回Redis中的数据----------");
               PageUtils result =  JSON.parseObject(searchBlogsJson,new TypeReference<PageUtils>(){});
               return result;
           }
           QueryWrapper<BlogEntity> wrapper = new QueryWrapper<BlogEntity>().eq("published",1);
           wrapper.and(w->{
               w.like("title", key).or()
                       .like("content", key).or().like("description", key);
           });
           IPage<BlogEntity> page = this.page(
                   new Query<BlogEntity>().getPage(params),
                   wrapper
           );
           List<BlogEntity> records = page.getRecords();
           records.forEach(System.out::println);

           List<FirstPageBlogVo> collect = records.stream().map(item -> {
               FirstPageBlogVo firstPageBlogVo = new FirstPageBlogVo();
               BeanUtils.copyProperties(item, firstPageBlogVo);
               firstPageBlogVo.setTypeName(typeService.getById(item.getTypeId()).getName());
               firstPageBlogVo.setNickname(userService.getById(item.getUserId()).getNickname());
               firstPageBlogVo.setAvatar(userService.getById(item.getUserId()).getAvatar());
               return firstPageBlogVo;
           }).collect(Collectors.toList());
           PageUtils pageUtils = new PageUtils(page);
           pageUtils.setList(collect);
//           collect.forEach(System.out::println);
           return pageUtils;
       }


    }


    @Override
    public DetailedBlogVo getDetailedBlog(Long id) {
        BlogEntity blogEntity = this.getById(id);

        DetailedBlogVo detailedBlogVo = new DetailedBlogVo();
        BeanUtils.copyProperties(blogEntity,detailedBlogVo);
        detailedBlogVo.setTypeName(typeService.getById(blogEntity.getTypeId()).getName());
        detailedBlogVo.setNickname(userService.getById(blogEntity.getUserId()).getNickname());
        detailedBlogVo.setAvatar(userService.getById(blogEntity.getUserId()).getAvatar());
        return detailedBlogVo;
    }

    @Override
    public BlogAndUserVo getBlogAndUserById(Long id) {

        BlogEntity entity = this.getById(id);
        // 文章访问数量自增
        this.baseMapper.update(entity,new UpdateWrapper<BlogEntity>().eq("blog_id",id)
                .set("views",entity.getViews()+1));
        this.baseMapper.getCommentCountById(id);
        BlogAndUserVo blogAndUserVo = new BlogAndUserVo();
        BeanUtils.copyProperties(entity,blogAndUserVo);
        blogAndUserVo.setNickname(userService.getById(entity.getUserId()).getNickname());

        String s = MarkdownUtils.markdownToHtml(entity.getContent());
        blogAndUserVo.setContent(s);


        return blogAndUserVo;
    }

    @Override
    public int getBlogTotal() {

      return   this.baseMapper.selectCount(new QueryWrapper<BlogEntity>().eq("published",1));
    }

    @Override
    public int getBlogViewTotal() {
      return this.baseMapper.getBlogViewTotal();
    }

    @Override
    public int getBlogMessageTotal() {
       return messageDao.selectCount(null);
    }

    @Override
    public int getBlogCommentTotal() {
        return commentDao.selectCount(null);
    }


}